/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> ulc2class.c </file>
  <brief>
    Generating header file for token definitions.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, April 2011.
  </author>
*/

#include "uls.h"

#ifdef WIN32
#define THIS_PROGNAME "Ulc2Class";
#else
#define THIS_PROGNAME "ulc2class";
#endif

char *progname;
char  *opt_class_name;
char *out_filepath;
int  prn_flags;

uls_lex_t *sam_lex;

#ifdef HAVE_GETOPT_H
#include <getopt.h>

static const struct option longopts[] = {
	{ "c-lang",        no_argument,       NULL, 'c' },
	{ "enum",          no_argument,       NULL, 'e' },
	{ "class-name",    required_argument, NULL, 'n' },
	{ "regular-toks",  no_argument,       NULL, 'r' },
	{ "reserved-toks", no_argument,       NULL, 'R' },
	{ "quote-toks",    no_argument,       NULL, 'q' },
	{ "output-path",   required_argument, NULL, 'o' },
	{ "silent",        no_argument,       NULL, 's' },
	{ "verbose",       no_argument,       NULL, 'v' },
	{ "version",       no_argument,       NULL, 'V' },
	{ "help",          no_argument,       NULL, 'h' },
	{ NULL, 0, NULL, 0 }
};
#endif

static void usage(void)
{
	printf("Usage: %s [OPTIONS] <ulc-file>\n\n", progname);
	printf("  or:  %s <ulc-file>\n\n", progname);
	printf("  or:  %s -c <ulc-file>\n\n", progname);
	printf("  or:  %s -c -e <ulc-file>\n\n", progname);
	printf("  or:  %s -n <class-name> <ulc-file>\n\n", progname);

	printf("ULS is an object factory for generic-purpose lexical analysis supporting UTF-8.");
	printf(" and provided as C/C++ libraries with a couple of tools for Linux/Windows platform.\n");
	printf("%s is one of the ULS-tools that generates a header-file defining tokens\n", progname);
	printf("By default, It generates a file having C++ class definition as follows\n");
	printf("\n");

	printf("#include \"UlsLex.h\"\n\n");
	printf("namespace AAA {\n");
	printf("  namespace BBB {\n");
	printf("     class SampleLex : public ULS::UlsLex {\n");
	printf("      public:\n");
	printf("       enum TokenType {\n");
	printf("          ERR = -1,\n");
	printf("          EOI,\n");
	printf("           ID,\n");
	printf("          NUM,\n");
	printf("           ... ...\n");
	printf("       };\n");
	printf("\n");
	printf("       SampleLex(std::string& ulc_file) : UlsLex(ulc_file) {}\n\n");
	printf("       virtual ~SampleLex() {}\n");
	printf("     }\n");
	printf("  }\n");
	printf("}\n");
	printf("\n");

	printf("With the generated header file and ULS libraries(libuls.*, libulscpp.*),\n");
	printf("you can tokenize input files or literal strings written for your programming language.\n");
	printf("For example, here is how one may use the ULS-library in simplest manner.\n");
	printf("\n");

	printf("#include \"sample_lex.h\"\n\n");
	printf("uls_lex_t *sample_lex;\n\n");
	printf("int main(int argc, char* argv[]) {\n\n");
	printf("    char *input_file = argv[1]\n\n");
	printf("    sample_lex=uls_create(\"sample\");\n\n");
	printf("    uls_push_file(sample_lex, input_file, 0);\n\n");
	printf("    for ( ; ; ) {\n\n");
	printf("        tok = uls_gettok(sample_lex);\n\n");
	printf("        if (tok == TOK_EOI) break;\n");
	printf("        // Here, use 'tok' and its lexeme 'uls_lexeme(sample_lex)'\n\n");
	printf("    }\n\n");
	printf("    uls_destroy(sample_lex);\n\n");
	printf("    return 0;\n\n");
	printf("}\n");
	printf("\n");

	printf("If you specify -c-option, you get the c-header file of token definitions.\n\n");
	printf("#define    TOK_ERR    -1\n\n");
	printf("#define    TOK_EOI     0\n\n");
	printf("#define    TOK_EOF     1\n\n");
	printf("#define    TOK_ID      2\n\n");
	printf("#define    TOK_NUMBER  3\n\n");
	printf("\n");

	printf("As shown above, the token numbers in ULS can be any integer");
	printf(" including negative numbers and in numbers in ASCII if there are no conflictions among them.\n");	
	printf("The following minimal ulc-file(sample.ulc) might be an input of %s of the above output.\n", progname);
	printf("\n");

	printf("# (1-line) comment start with the sharp-character.\n\n");
	printf("FILE_VERSION: 2.0  # The version of ulc-format itselfcurrently is 2.0\n\n");
	printf("VERSION: 1.0 # The version of the content of this file.\n\n");
	printf("\n");
	printf("%%  # Separator\n\n");
	printf("TOK_INT      int    256\n\n");
	printf("TOK_FLOAT    float\n\n");
	printf("\n\n");
	printf("The ulc-file is composed of 2 sections.\n");
	printf("The first one has a few of options setting lexical configuration\n");
	printf("and the second is a list of token definitions, one per line.\n");
	printf("\n");

	printf("The lex-options in the first section contains multiple of lines where each line represents one option.\n");
	printf("A lex-option starts with its option name trailed by colon character as 'COMMENT_MARKER:'\n");
	printf("Be sure not to separate the ':' from 'COMMENT_MARKER'.\n");
	printf("You can include the following options.\n");
	printf("\n");

	printf("COMMENT_MARKER: <start-comment-marker> <end-comment-marker>\n");
	printf("    COMMENT_MARKER specifies a comment-style.\n");
	printf("    For the familiar C-style comment, this can be like this.\n");
	printf("    COMMENT_MARKER: /* */\n");
	printf("\n");
	
	printf("QUOTE_MARKER: <start-quote-marker> <end-quote-marker> [verbatim]\n");
	printf("    QUOTE_MARKER specifies a literal string style in your language.\n");
	printf("    For the familiar C-style quotation, this can be like this.\n");
	printf("         QUOTE_MARKER: \" \"\n\n");
	printf("         QUOTE_MARKER: ' '\n\n");
	printf("    By default, ULS gives the literal string with escape-chars processed,\n");
	printf("    but if you want to get the unanalyzed literal string, ");
	printf("    append the 'verbatim' keyword to the end of the line.\n");
	printf("\n");

	printf("ID_FIRST_CHARS: <charset1> <charset2> ...\n");
	printf("    ID_FIRST_CHAR specifies the charsets in which character can be the first-char of identifier.\n");
	printf("    ID_FIRST_CHAR contains the alpahbet, precisely, [A-Z]|[a-z], as default charset.\n");
	printf("    The charsets you specify will be just added to to the default charset.\n");
	printf("    Each <charset> may be one-char '$', '.' 0x24, or a range of chars like 0x34AB-45EF representing (decoded) UTF-8.\n");
	printf("    Specify the characters without the quotation mark as follows\n");
	printf("\n");
	printf("       ID_FIRST_CHARS: $ . 0x24 0x34AB-45EF\n\n");
	printf("\n");

	printf("ID_CHARS: <charset1> <charset2> ...\n");
	printf("    ID_CHAR specifies the charset to be added to default id-char-set.\n");
	printf("    The default of 'id-char-set' is consist of alphabet or digits(0,...9), precisely, [A-Z]|[a-z]|[0-9].\n");
	printf("    Each <charset> may be one-char or a range of chars like 0x34AB-45EF representing (decoded) UTF-8.\n");
	printf("\n");

	printf("ICNST_SUFFIXES: <suffix1> <suffix2> ...\n");
	printf("FCNST_SUFFIXES: <suffix1> <suffix2> ...\n");
	printf("   ICNST_SUFFIXES and FCNST_SUFFIXES is for specify the constant suffixes of integer or floating-number.\n");
	printf("   A majority of programming languages calls for suffix for number like 'UL', 'ul', 'LU', 'L', 'f'.\n");
	printf("   For example,\n");
	printf("\n");
	printf("      ICNST_SUFFIXES: UL ul LU L\n");
	printf("\n");

	printf("RENAME: [TOK_ERR|TOK_EOI|TOK_EOF|TOK_ID|TOK_NUMBER|TOK_LINENUM|TOK_NONE] <new-name>\n");
	printf("   ULS uses internally a few of reserved keyword for identifier, number, end-of-input, end-of-file, ...\n");
	printf("   Their names are 'TOK_ID', 'TOK_NUMBER', 'TOK_EOI', 'TOK_EOF', ...\n");
	printf("   If you wanna change these reserved names or even the token-id, use 'RENAME:' option-specifier.\n");
	printf("\n");

	printf("CASE_SENSITIVE: [true|false]\n");
	printf("   If the keyword and id are case insensitive in your language,\n");
	printf("   you can do it by using this option-specifier 'CASE_SENSITIVE:'.\n");
	printf("   The default value is true.\n");
	printf("\n");

	printf("%s provides with the following command line options.\n", progname);
	printf("  -c, --c-lang               generates header file for c-language.\n");
	printf("  -e, --enum                 generates enum sturcture in c header file, instead of '#define ...'\n");
	printf("  -n, --class-name=<name>    specify the name of class(or enum-name).\n");
	printf("  -r, --regular-toks         only regular tokens generated.\n");
	printf("  -R, --reserved-toks        only reserved tokens generated.\n");
	printf("  -q, --quote-toks           only quote tokens generated.\n");
	printf("  -o, --output-path=<output> specify the pathname of the output file(*.h).\n");
	printf("  -v, --verbose              verbose mode.\n");
	printf("  -V, --version              prints the version information and exit.\n");
	printf("  -h, --help                 display this help and exit.\n");
	printf("");

	printf("The 'regular-toks' is the tokens that were defined by you.\n");
	printf("The 'reserved-toks' is the basic tokens that ULS initially defined,\n");
	printf("   such as TOK_ERR, TOK_EOI, TOK_EOF, TOK_ID, TOK_NUM, ...\n");
	printf("\n");
	
	printf("You can specify the long name containg namespaces for -n-option");
	printf(" like 'AAA.BBB.SampleLex', or just class name 'SampleLex'.\n");

	printf("To generate a c++ header,\n");
	printf("    %s sample.ulc\n", progname);
	printf("    %s -n AAA.BBB.SampleLex sample.ulc\n", progname);
	printf("\n");

	printf("To generate a c header file(*.h),\n");
	printf("    %s -c sample.ulc\n", progname);
	printf("    %s -c -e sample.ulc\n", progname);
	printf("    %s -c -e -n e_name sample.ulc\n", progname);
	printf("\n");

	printf("For more detailed usage about ULS,");
	printf(" refer to the examples in the directory 'examples' or 'tests' in top directory of the package.\n");
}

static int
ulc2class_options(int opt, char* optarg)
{
	int   stat = 1;

	switch (opt) {
	case 'e':
		prn_flags |= ULS_FL_C_ENUM | ULS_FL_C_CONSTANTS_GEN;
		break;

	case 'c':
		prn_flags |= ULS_FL_C_CONSTANTS_GEN;
		break;

	case 'n':
		opt_class_name = optarg;
		break;

	case 'r':
		prn_flags |= ULS_FL_WANT_REGULAR_TOKS;
		break;

	case 'R':
		prn_flags |= ULS_FL_WANT_RESERVED_TOKS;
		break;

	case 'q':
		prn_flags |= ULS_FL_WANT_QUOTE_TOKS;
		break;

	case 'o':
		out_filepath = optarg;
		break;	

	case 'h':
		usage();
		stat = 0;
		break;

	case 's':
		prn_flags |= ULS_FL_SILENT;
		break;

	case 'v':
		prn_flags |= ULS_FL_VERBOSE;
		break;

	case 'V':
		printf("%s %s, written by %s,\n\tis provided under %s.\n",
			progname, ULF_GEN_PROGVER, ULS_AUTHOR, ULS_LICENSE_NAME);
		stat = 0;
		break;

	default:
		err_log("undefined option -%c", opt);
		stat = -1;
		break;
	}

	return stat;
}

static int
parse_options(int argc, char* argv[])
{
#ifdef HAVE_GETOPT_H
	int  rc, opt, longindex;
#endif
	int   i0, mask;

#ifdef HAVE_GETOPT_H
	while ((opt=getopt_long(argc, argv, "cen:rRqo:svVh", longopts, &longindex)) != -1) {
		if ((rc=ulc2class_options(opt, optarg)) <= 0) {
			return rc;
		}
	}
	i0 = optind;
#else
	if ((i0=uls_getopts(argc, argv, "cen:rRqo:svVh", ulc2class_options)) <= 0) {
		return i0;
	}
#endif

	mask = ULS_FL_WANT_REGULAR_TOKS | 
		 	ULS_FL_WANT_QUOTE_TOKS | ULS_FL_WANT_RESERVED_TOKS;
	if (!(prn_flags & mask)) prn_flags |= mask;

	return i0;
}

static int
fmtproc_tabs(uls_lf_context_t* ctx, uls_lf_puts_t puts_proc)
{
	char buf[16];
	int i, len, buf_siz;

	buf_siz = sizeof(buf);
	memset(buf, '\t', buf_siz);

	len = (int) va_arg(ctx->args, int);

	for (i=0; i<len/buf_siz; i++)
		puts_proc(ctx->x_dat, buf, buf_siz);

	puts_proc(ctx->x_dat, buf, len%buf_siz);

	return len;
}

int
main(int argc, char* argv[])
{
	char specname[ULS_LEXSTR_MAXSIZ+1];
	char *config_file;
	char *ptr0, *ptr;
	int i0, len;
#ifdef WIN32DEBUG
	initialize_uls();
#endif
	uls_lf_register_convspec(NULL, "t", fmtproc_tabs);
	progname = THIS_PROGNAME;

	if ((i0=parse_options(argc, argv)) <= 0) {
		if (i0 < 0) err_log("Incorrect use of command options.");
		return i0;
	}

	if (i0 >= argc) {
		err_log("%s(%s), written by %s,\n\tis provided under %s.",
			progname, ULF_GEN_PROGVER, ULS_AUTHOR, ULS_LICENSE_NAME);
		err_log("Type '%s -h' for usage.", progname);
		return 0;
	}

	config_file = argv[i0];

	ptr0 = uls_filename(config_file);
	// ptr0: filename of config_file

	if ((ptr = strrchr(ptr0, '.'))!=NULL && uls_streql(ptr+1, "ulc")) {
		len = (int) (ptr - ptr0);
	} else {
		err_log("The config-file '%s' must have the suffix 'ulc'", config_file);
		return -1;
	}

	if (len<=0 || len > ULS_LEXSTR_MAXSIZ) {
		err_log("%s: too long spec-name", ptr0);
		return -1;
	}

	strncpy(specname, ptr0, len);
	specname[len] = '\0';

	if ((sam_lex=uls_create(config_file)) == NULL) {
		err_log("can't init uls-lexer");
		return -1;
	}

	if (uls_generate_tokdef_file(sam_lex, out_filepath, specname, opt_class_name, prn_flags) < 0)
		err_log("fail to make the header files for %s", config_file);

	uls_destroy(sam_lex);

	return 0;
}
